Drawing
Lines
Last time we discussed the basic elements of polygon construction
under OpenGL. OpenGL only supports a few basic primitive geometrical
objects: points, lines, polygons, and surfaces described by arrays
of small quadrilaterals or triangles.
The main idea behind OpenGL's simplicity is that it is up to the
developer to implement from this simple objects more complex
geometrical models. OpenGL contains a number of commands to control
the details of points, lines and polygons.
For example the size of points can be specified in pixels using
glPointSize:
void glPointSize(GLfloat
size)
By the default the size of points is 1.0 and size must
always be greater than zero. Notice that the size of a point is
specified by a float number; fractional point and line sizes are
allowed. OpenGL interprets fractional pixel sizes according to the
rendering context. If the anti-aliasing mode is enable then OpenGL
modifies the neighboring pixels to the line in question in order to
give the appearance of a fractional width. Anti-aliasing is a
technique also used to eliminate the ugly stars the straight lines
show on computer screens at low monitor resolution. If anti-aliasing
is not enable then glPointSize will round off size
to the closets integer.
The physical size of a pixel actually is device dependent. So for
example at low monitor resolution a pixel appears wider. Similarly
on very high resolution devices, like a plotter, the default 1 pixel
line can appear almost invisible. To estimate the real width of your
lines you must know the actual physical dimensions of pixels on the
output device.
The width of lines is specified by the glLineWidth
function, that must be invoked before the glBegin() -
glEnd() pair that draws the line(s). Here is the full syntax of
the command:
void glLineWidth(GLfloat
width)
OpenGL implementations may limit the width of nonantialiased
lines to its maximum antialiased line width, rounded to the nearest
integer value. Also keep in mind that line widths are measured not
perpendicularly to the line but in the y-direction if the absolute
value of the slope is less than 1; if greater in the x-direction.
This month we have prepared another simple but hopefully useful
2D animation that shows you how to use various kinds of line widths
in your OpenGL applications (example2.c,
Makefile).
I chose an example from Quantum Physics, a quantum particle trapped
in a double well potential. Why? Umm actually I forget. Anyway I
figure that it would be useful for physics and engineering students
to see how to integrate the time dependent Schroedinger equation,
others may just enjoy watching the non-intuitive nature of quantum
mechanics. A particle in QM is not represented by a position and a
velocity but by a "wave" a quantum wave (solid purple line in our
animation) whose absolute square value represents the probability of
observing the particle at a given position (dash white line):
Figure 1. Quantum Simulation Snapshot
For those with some course work in Ordinary Differential
Equations I can tell you that the wave equation is integrated using
a FFT (Fast Fourier Transform) Split-Operator method. This method is
far more accurate and rapid than any finite difference method. It is
applicable to nonlinear wave propagation; the time evolution
operator is split to second (or higher order) into operators only
dependent on either position and momentum (frequency), then the
wavefunction is evolved in time by successively applying these
operators switching back and forth between the position and momentum
(frequency) space.
The body of the source code can be used for many other
applications. You can swap my quantum simulation with your own time
dependent function and get a nice animation of your system. You
could also try to write a simplified OpenGL-based gnuplot for
plotting functions and data files.
If the reader has followed the previous articles on GLUT and
OpenGL this source code will appear too simple and easy to
understand (of course, quantum mechanics aside). There is nothing
extraordinary going here. In the main() we open a single
window in double-buffer mode, then we pass a display() and
idle() callback functions that take care of plotting the
wavefunction and integrating the wave equation respectively. Again,
do mind what goes on in the idle() function, although it is a very
beautiful trick is not necessary to fully understand it to grasp the
content of this article. The really new OpenGL stuff is in the
display callback function: void
display (void)
{
static char label[100];
float xtmp;
/* Clean drawing board */
glClear (GL_COLOR_BUFFER_BIT);
/* Write Footnote */
glColor3f (0.0F, 1.0F, 1.0F);
sprintf (label, "(c)Miguel Angel Sepulveda 1998");
glRasterPos2f (-1.1, -1.1);
drawString (label);
/* Draw fine grid */
glLineWidth (0.5);
glColor3f (0.5F, 0.5F, 0.5F);
glBegin (GL_LINES);
for (xtmp = -1.0F; xtmp < 1.0F; xtmp += 0.05)
{
glVertex2f (xtmp, -1.0);
glVertex2f (xtmp, 1.0);
glVertex2f (-1.0, xtmp);
glVertex2f (1.0, xtmp);
};
glEnd ();
/* Draw Outsite box */
glColor3f (0.1F, 0.80F, 0.1F);
glLineWidth (3);
glBegin (GL_LINE_LOOP);
glVertex2f (-1.0F, -1.0F);
glVertex2f (1.0F, -1.0F);
glVertex2f (1.0F, 1.0F);
glVertex2f (-1.0F, 1.0F);
glEnd ();
/* Draw Grid */
glLineWidth (1);
glColor3f (1.0F, 1.0F, 1.0F);
glBegin (GL_LINES);
for (xtmp = -0.5; xtmp < 1.0; xtmp += 0.50)
{
glVertex2f (xtmp, -1.0);
glVertex2f (xtmp, 1.0);
glVertex2f (-1.0, xtmp);
glVertex2f (1.0, xtmp);
};
glEnd ();
/* Draw Coordinate Axis */
glLineWidth (2);
glBegin (GL_LINES);
glVertex2f (-1.0, 0.0);
glVertex2f (1.0, 0.0);
glVertex2f (0.0, -1.0);
glVertex2f (0.0, 1.0);
glEnd ();
/* Axis Labels */
glColor3f (1.0F, 1.0F, 1.0F);
sprintf (label, "Position");
glRasterPos2f (0.80F, 0.025F);
drawString (label);
glColor3f (1.0F, 0.0F, 1.0F);
sprintf (label, " Quantum Probability ");
glRasterPos2f (0.025F, 0.90F);
drawString (label);
glColor3f (1.0F, 1.0F, 1.0F);
sprintf (label, " Real(Psi) ");
glRasterPos2f (0.025F, 0.85F);
drawString (label);
/* Draw Wavefunction */
psiDraw (NR_POINTS, psi, x);
/* Draw potential Function */
potentialDraw (NR_POINTS, potential, x);
glutSwapBuffers ();
};
The first thing done is to clear the color buffer bit, this gives
us a clean (black) drawing board. Then we add a footnote using
glRasterPos and glutBitmapCharacter (drawstring is
nothing but a wrapper for the clut utility). In future lessons
glRasterPos will appear again as an auxiliary function for
texture rendering. Neither OpenGL nor GLUT offer a simple and
powerful way for rendering text onto a graphic window. The
glutBitmapCharacter basically rasters a font bitmap onto the color
buffer.
Following the footnote comes a number of lines: the outside box,
the background grid, the coordinate axis, and of course the current
curves drawn with psiDraw and potentialDraw.
Before every line rendered is a glLineWidth that specifies
the number of pixels of width to be given to the line. Figure 1
shows the output on an Xwindow System (Linux Alpha). For some
unknown reason to me the Windows 95 output of the same program looks
very crappy, it appears as if the antialiasing feature is not well
supported by the SGI OpenGL driver; it is hard ti differentiate
lines that in principle should have different widths, and the
background grid of lines also appears very uniform. These defects
appear when the display is set at high resolution so it is not an
artifact of a low resolution monitor setup. I am happy to say that
Linux X window system defeats by large win95/NT once more.
There are two types of line rendering in the display()
function, GL_LINES mode which joins vertices with a continuous open
line and GL_LINE_LOOP mode that at the end closes the loop.
Antialiasing
Lines
I have enabled antialiasing for the lines in the
reshape() callback function, void
reshape (int w, int h)
{
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glViewport (0, 0, w, h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluOrtho2D (-1.2, 1.2, -1.2, 1.2);
glEnable (GL_LINE_SMOOTH); /* Enable Antialiased lines */
glEnable (GL_LINE_STIPPLE);
};
What is GL_LINE_STIPPLE for? OpenGL let us control not only the
width of a line but also its pattern. By enabling GL_LINE_STIPPLE we
are able to draw dash or any other pattern of lines. The only
stippled line in the animation appears in the psiDraw()
function: glLineWidth (1);
glPushAttrib (GL_LINE_BIT);
glLineStipple (3, 0xAAAA);
glBegin (GL_LINE_STRIP);
for (i = 0; i < nx; i++)
{
xs = ratio1 * (x[i] - XMIN) - 1.0;
ys = ratio2 * (psi[2 * i] - YMIN) - 1.0;
glVertex2d (xs, ys);
};
glEnd ();
glPopAttrib ();
Line
Stippling
The glLineStipple specifies the pattern used for
stippling, in our example we used the pattern 0xAAAA. In binary this
numbers read as 0000100010001000 and OpenGL interprets this drawing
3 bits off, 1 bit on, 3 bits off, 1 bit on, 3 bits off, 1 bit on and
finally 4 bits off. Yes the pattern is read backwards because the
low order bits are used first. Now glLineStipple gets two
parameters, the stippled pattern which should be an hexadecimal
number and and integer factor which serve to scale the pattern, so
with a factor of 3 our stippled line will show 9 bits off, 3 bits
on, 9 bits off, 3 bits on, 9 bits off, 3 bits on and finally 12 bits
off. Playing with factors and binary patterns one can draw all sort
of complicated stippled lines.
One more detail: I have enclosed the stippled line rendering
between a push and pop Attribute statement. Remeber when in our
first article we mentioned that OpenGL is a state machine? Well in
future articles we will see in more detail these push and pop
operations, but in short what we are doing with the first
glPushAttrib (GL_LINE_BIT) is to push in a stack the
current value of the GL_LINE_BIT state variable (this variables
decides the stippling pattern), then we can alter GL_LINE_BIT with
our glLineStipple statement and when we are done we call a
glPopAttrib that brings back the old GL_LINE_BIT variable.
This mechanism is an effective way to modified the state variables
of the OpenGL machine locally. If we didn't do this then all lines
draw after our glLineStipple would have the same stippling
pattern and we would be forced to declare a glLineStipple
patter for every line we ever render in our application. Push &
Pop saves us this annoying work.
Next Time
....
OpenGL is famous for is wonderful 3D API interface. So far we
have explored some elemental possibilities of 2D rendering with
OpenGL. Next time we will examine the 3D OpenGL scene, how to set a
perspective, system of coordinates, clipping planes and projection
matrixes.
Till then have fun with OGL...... |